# -*- coding: utf-8 -*-
###
# Wifi + GPS fingerprint collection tool
# Copyright (c) 2008, Yura Zenevich, Jorge Silva, and Jamon Camisso
# Homepage http://wiki.scyp.atrc.utoronto.ca/wiki/WiFiPositioning
# Contact scyp@atrc.utoronto.ca
#    
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program, see enclosed file gpl-3.txt or browse to
# <http://www.gnu.org/licenses/gpl.txt>
###

import wx
import os
import string
import urllib
import time

import sqlite3

from Scan import *
from Address import *
from Search import *
from WifiScanner import *
from WifiFinder import *
from gps_scan import *


class Interface(wx.Frame):

	'''	Application window's constructor. Initializes all components and
		their properties.'''
	def __init__(self, parent, ID, title):

		wx.Frame.__init__(self, parent, ID, title, wx.DefaultPosition, \
			wx.Size(850, 600))

		self.CreateStatusBar()
		self.SetStatusText("Ready")
		self.mainPanel = wx.Panel(self, -1)

		self.chkSelectMode = wx.CheckBox(self.mainPanel, -1, "Collection Mode  |")
		self.chkShowScans = wx.CheckBox(self.mainPanel, -1, "Show known locations")

		self.lblSelectBuilding = wx.StaticText(self.mainPanel, -1, \
			"  Select Building:   ")

		self.lstBuilding = wx.ComboBox(self.mainPanel, -1, \
		choices=Addresses().ReturnBuildingCodeNamePairs(), \
		style=wx.CB_DROPDOWN)
		
		self.lstBuilding.SetSelection(0)
		self.lblSelectFloor = wx.StaticText(self.mainPanel, -1, \
			"Select Floor:   ")
		
		self.lstFloor = wx.ComboBox(self.mainPanel, -1, \
			choices=Addresses().ReturnFloorNumbers( \
			Addresses().ReturnBuildingIdsFromCodeNamePairs( \
			self.lstBuilding.GetValue())), style=wx.CB_DROPDOWN|wx.CB_READONLY)

		self.lstFloor.SetSelection(0)
		self.cmdPerformSearch = wx.Button(self.mainPanel, -1, \
			"Find me!")
		self.cmdClearImage = wx.Button(self.mainPanel, -1, \
			"Clear")

		self.imgWindow = wx.ScrolledWindow(self)		
		self.imgMap = wx.StaticBitmap(self.imgWindow, -1, \
			wx.Bitmap(self.UpdateImageURL(), wx.BITMAP_TYPE_ANY))

		self.txtOutput = wx.TextCtrl(self, -1, "", \
			style=wx.TE_MULTILINE|wx.TE_READONLY|wx.NO_BORDER)
		
		self.txtOutput.SetMinSize((200, 445))
		self.UpdateScrollWindowSize()

		self.BindAll()
		self.__do_layout()

	# end __init__
	
	'''	Updates the image scroll window size. Called whenever we load a
		new image or when the size of the main application window is 
		changed.'''
	def UpdateScrollWindowSize(self):

		frameSize = self.GetSizeTuple()
		panelSize = self.mainPanel.GetSizeTuple()
		textSize = self.txtOutput.GetSizeTuple()
		imageSize = self.imgMap.GetSizeTuple()
		statusBarSize = self.GetStatusBar().GetSizeTuple()

		imgWindowSize = (min(frameSize[0] - textSize[0], \
			imageSize[0]), min(imageSize[1], frameSize[1] - panelSize[1] - \
			statusBarSize[1]))

		self.imgWindow.SetMaxSize(imgWindowSize)				
		self.imgWindow.SetScrollbars(50, 50, imageSize[0] / 50, \
			imageSize[1] / 50)

	# end UpdateScrollWindowSize
	
	'''	Redraws image scroll window and updates the layout whenever the
		size of the main window is changed.'''
	def Resize(self, event):

		self.UpdateScrollWindowSize()
		self.__do_layout()

		event.Skip()

	# end Resize

	'''	Provides all the event binders for all controllers used in the 
		application.'''
	def BindAll(self):

		self.Bind(wx.EVT_SIZE, self.Resize, self)
		self.lstBuilding.Bind(wx.EVT_TEXT_ENTER, self.QueryBuildings)
		self.lstBuilding.Bind(wx.EVT_COMBOBOX, self.UpdateFloorList)
		self.lstFloor.Bind(wx.EVT_COMBOBOX, self.UpdateImage)
		self.cmdPerformSearch.Bind(wx.EVT_BUTTON, self.PerformSearch)
		self.cmdClearImage.Bind(wx.EVT_BUTTON, self.ClearImage)
		self.imgMap.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftClick)
		self.chkShowScans.Bind(wx.EVT_CHECKBOX, self.OnShowScans)

	# end BindAll

	''' Whenever we check "Show Scans" checkbox, update the floor plan and 
		display the previous scan points for the reference, if unchecked removes
		such points (reloads empty image).'''
	def OnShowScans(self, event):
		
		if self.chkShowScans.GetValue() == True:
			scans = Search().FindAllScansOnTheFloor(
				Addresses().ReturnBuildingFloorId( \
				Addresses().ReturnBuildingIdsFromCodeNamePairs( \
				self.lstBuilding.GetValue()), self.lstFloor.GetValue()))
			if scans is not None:
				bmp = self.imgMap.GetBitmap()
				dc = wx.MemoryDC()
				dc.SelectObject(bmp)

				dc.BeginDrawing() #	BEGIN DRAWING

				dc.SetBackgroundMode(wx.TRANSPARENT)
				dc.SetBrush(wx.TRANSPARENT_BRUSH)

				dc.SetPen(wx.Pen("BLACK",3))		
				for scanCoord in scans:
					dc.DrawCircle(scanCoord[0], scanCoord[1], 3)
		
				dc.EndDrawing() # END DRAWING

				self.imgMap.SetBitmap(bmp)
				self.UpdateScrollWindowSize()
				self.__do_layout()
			#endif
			event.Skip()
		else:
			self.UpdateImage(event)			
		
	# end OnShowScans

	''' Handles the "Clear Floor Plan" button: removes the search results 
		(loads clean floor plan from the source).'''
	def ClearImage(self, event):

		self.UpdateImage(event)

	# end ClearImage

	'''	Whenever user types part of the building name or building code
		in the buildings list text field and presses enter, queires the
		database for match from building tables, repopulates 
		lstBuildings only with matches.'''
	def QueryBuildings(self, event):

		criteria = self.lstBuilding.GetValue()
		matchingBuildings = Addresses().FindBuildingCodeNamePairs( \
			criteria)

		self.lstBuilding.Clear()
		for match in matchingBuildings:
			self.lstBuilding.Append(match)
		#endfor

		event.Skip()

	# end QueryBuildings
	
	'''	Handles the perform search button presses. Runs WifiFind() from
		either WifiFinder or WifiFinder2 and draws the output: either
		distances from existing points (WifiFinder) or the estimated 
		point (WifiFinder2).'''
	def PerformSearch(self, event):

		coordsAndFloor = WifiFind()
		point_both = coordsAndFloor[0]
		point_me = coordsAndFloor[1]
		point_jorge = coordsAndFloor[2]
		buildingName = coordsAndFloor[3]
		floorNumber = coordsAndFloor[4]
		r1 = coordsAndFloor[5]
		r2 = coordsAndFloor[6]
		rcomb = coordsAndFloor[7]

		print buildingName, 'Floor:', floorNumber
		print 'Coordinate:', point_both, 'Accuracy:',rcomb

		bmp = self.UpdateBuildingAndFloor(floorNumber, buildingName)
		extent = Addresses().GetExtent(floorNumber, buildingName)
		dc = wx.MemoryDC()
		dc.SelectObject(bmp)

		dc.BeginDrawing() #	BEGIN DRAWING

		dc.SetBackgroundMode(wx.TRANSPARENT)
		dc.SetBrush(wx.TRANSPARENT_BRUSH)

		# COMBINED METHOD
		#dc.SetPen(wx.Pen("BLUE",3))		
		#dc.DrawCircle(point_both[0], point_both[1], rcomb)
		
		# YURA'S METHOD
		#dc.SetPen(wx.Pen("RED",3))
		#dc.DrawCircle(point_me[0], point_me[1], r1)

		# JORGE'S METHOD
		dc.SetPen(wx.Pen("GREEN",3))
		dc.DrawCircle(point_jorge[0], point_jorge[1], r2)

		dc.EndDrawing() # END DRAWING

		self.imgMap.SetBitmap(bmp)
		self.chkShowScans.SetValue(False)
		self.UpdateScrollWindowSize()
		self.__do_layout()

		event.Skip()

	''' Given the floor number string and the name of the building, select the 
		building as well as the floor by accodingly. Return a bitmap for that
		floor.'''
	def UpdateBuildingAndFloor(self, floorNumber, buildingName):

		matchingBuilding = Addresses().FindBuildingCodeNamePairs( \
			buildingName)[0]

		if self.lstBuilding.GetValue() == matchingBuilding and \
			self.lstFloor.GetValue() == floorNumber:

			return self.imgMap.GetBitmap()

		else:
			self.lstBuilding.Clear()
			self.lstBuilding.Append(matchingBuilding)
			self.lstBuilding.SetSelection(0)
	
			self.lstFloor.Clear()
			self.lstFloor.Append(floorNumber)
			self.lstFloor.SetSelection(0)

			return wx.Bitmap(self.UpdateImageURL())

	# end UpdateBuildingAndFloor

	'''	Repopulates the list of floors whenever new building is 
		selected. Automatically selects first floot in the list and 
		calls UpdateImage to update the drawing to a new floor.'''
	def UpdateFloorList(self, event):

		try:
			newFloors = Addresses().ReturnFloorNumbers( \
				Addresses().ReturnBuildingIdsFromCodeNamePairs( \
				self.lstBuilding.GetValue()))
		except:
			print "There is no floor information available for this building."

		self.lstFloor.Clear()

		for floor in newFloors:
			self.lstFloor.Append(floor)
		#endfor

		self.lstFloor.SetSelection(0)
		self.UpdateImage(event)

	# end UpdateFloorList

	'''	Called whenever the building or building floor is changed. 
		Updates the drawing of the floor plan for the building-floot.'''
	def UpdateImage(self, event):

		self.chkShowScans.SetValue(False)
		try:
			self.imgMap.SetBitmap(wx.Bitmap(self.UpdateImageURL()))
		except:
			print "Image is Unavailable."
		self.UpdateScrollWindowSize()
		self.__do_layout()

		event.Skip()

	# end UpdateImage

	'''	Grabs images from the scyp server. Builds a proper path by 
		querying the database and applying appropriate file name 
		corresponding to the floor.'''
	#	FIXME	Need to implement some kind of .htaccess rule to prevent
	#	outside (to U of T) access.
	def UpdateImageURL(self):
	
		url = ""

		try:
			url_get = urllib.urlopen("http://localhost/latinoandroid/floors/")
			if url_get:
				url = "http://localhost/latinoandroid/floors/"
			else:
				print "webserver?"	
				url = "http://scyp.atrc.utoronto.ca/media/images/wips/"
		except:
			print "local webserver not running"
			url = "http://scyp.atrc.utoronto.ca/media/images/wips/"

		url = url + Addresses().ReturnPaths(Addresses().ReturnBuildingFloorId( \
			Addresses().ReturnBuildingIdsFromCodeNamePairs( \
			self.lstBuilding.GetValue()), self.lstFloor.GetValue()))[0]

		print url
		tempImg = 'tmpImg.png'
		image = urllib.urlretrieve(url, tempImg)

		return tempImg

	# end UpdateImageURL

	'''	Compose the layout of the application.'''
	def __do_layout(self):

		sizerMain = wx.BoxSizer(wx.VERTICAL)
		sizerHorizontal = wx.BoxSizer(wx.HORIZONTAL)
		sizerBitmap = wx.BoxSizer(wx.VERTICAL)

		sizerPanel = wx.BoxSizer(wx.HORIZONTAL)
		sizerPanel.Add(self.chkSelectMode, 0, wx.ALIGN_CENTER_HORIZONTAL| \
			wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
		sizerPanel.Add(self.lblSelectBuilding, 0, wx.ALIGN_CENTER_HORIZONTAL| \
			wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
		sizerPanel.Add(self.lstBuilding, 0, wx.ALIGN_CENTER_HORIZONTAL| \
			wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
		sizerPanel.Add(self.lblSelectFloor, 0, wx.ALIGN_CENTER_HORIZONTAL| \
			wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
		sizerPanel.Add(self.lstFloor, 0, wx.ALIGN_CENTER_HORIZONTAL| \
			wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
		sizerPanel.Add(self.cmdPerformSearch, 0, wx.ALIGN_CENTER_HORIZONTAL| \
			wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
		sizerPanel.Add(self.cmdClearImage, 0, wx.ALIGN_CENTER_HORIZONTAL| \
			wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)
		sizerPanel.Add(self.chkShowScans, 0, wx.ALIGN_CENTER_HORIZONTAL| \
			wx.ALIGN_CENTER_VERTICAL|wx.ADJUST_MINSIZE, 0)

		self.mainPanel.SetSizer(sizerPanel)

		sizerBitmap.Add((20, 0), 0, wx.ADJUST_MINSIZE, 0)
		sizerBitmap.Add(self.imgWindow, 0, wx.EXPAND, 0)
		
		sizerHorizontal.Add(sizerBitmap, 1, wx.EXPAND, 0)
		sizerHorizontal.Add(self.txtOutput, 0, wx.ADJUST_MINSIZE, 0)

		sizerMain.Add(self.mainPanel, 0, 0, 0)
		sizerMain.Add(sizerHorizontal, 1, wx.EXPAND, 0)

		self.SetSizer(sizerMain)
		self.Layout()

	# end __do_layout

	'''	When double clicking, check time for timestamp, get xy offset, 
		find mac addresses, get gps coordinates.'''
	def OnLeftClick(self, event):

		if self.chkSelectMode.GetValue() == True:
			ScanTime = time.time()
			Point = self.imgWindow.CalcUnscrolledPosition(event.GetPosition())
			PointX = Point[0]
			PointY = Point[1]
			WifiScanOutput = []

			k = 1
			while k < 2:
				Details = MultiScan(8,4)
				k = k + 1
				time.sleep(1.5)
			Macs = Details[0]
			Signals = Details[2]
			Counts = Details[1]

			gps = gps_scan()
			DecimalLatitude = gps[0]
			DecimalLongitude = gps[1]
			FixData = gps[2]
			SatelliteNumber = gps[3]
		
			BuildingFloorId = Addresses().ReturnBuildingFloorId( \
				Addresses().ReturnBuildingIdsFromCodeNamePairs( \
				self.lstBuilding.GetValue()), self.lstFloor.GetValue())

			i = 0
			while i < len(Macs):
				WifiScanOutput.append("MAC: " + Macs[i] + " - Strength:	" + \
					str(Signals[i]) + "\n")
				i = i + 1
			#endwhile
	
			GPSOutput = ("LAT: %s" + " - LONG: %s\n") %(DecimalLatitude, \
			DecimalLongitude)
			XYOutput = "XY Coordinate: %s" %(str(Point))
			TextOutput = ''.join(WifiScanOutput) + GPSOutput + XYOutput

			self.txtOutput.SetValue(TextOutput)
		
			Scan().RecordNewScan(BuildingFloorId, ScanTime, PointX, \
				PointY, FixData, DecimalLatitude, DecimalLongitude, \
				SatelliteNumber)
		
			i = 0
			while i < len(Macs):
				Scan().RecordNewWifiScan(Macs[i], Signals[i], \
				ScanTime, Counts[i])
				i = i + 1
			#endwhile

		#endif
		print "Scan terminato!"
	
		event.Skip()

	# end OnLeftClick

class MyApp(wx.App):
	def OnInit(self):
		frame = Interface(None, -1, "WiFi Positioning Tool")
		frame.Show(True)
		self.SetTopWindow(frame)
		return True

app = MyApp(0)
app.MainLoop()
